package com.lsh.oms.core.records;

import com.lsh.base.lang.Lists;
import com.lsh.base.lang.Maps;
import com.lsh.base.lang.json.Jsons;
import com.lsh.oms.core.dao.other.ReturnResultMapper;
import com.lsh.oms.core.mq.MessageProducer;
import com.lsh.oms.core.records.core.JsonType;
import com.lsh.oms.core.records.core.Return;
import com.lsh.oms.core.records.core.ReturnRequest;
import com.lsh.oms.core.records.core.ReturnResult;
import com.lsh.oms.core.service.order.OrderQueryService;
import com.lsh.oms.core.service.po.OmsTaskService;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

import static com.lsh.base.lang.BigDecimals.*;
import static com.lsh.base.lang.bean.Beans.*;
import static java.math.BigDecimal.ZERO;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;

public abstract class ReturnResultRecord implements ReturnResult, JsonType {

    @Resource
    protected ReturnResultMapper mapper;

    @Resource
    protected OmsTaskService tasks;

    @Value("${kafka.topic.of.return.result:oms_return_results}")
    protected String topic;

    @Resource(name = "kafka.message.producer")
    protected MessageProducer mq;

    @Resource
    protected OrderQueryService orders;

    @Resource
    protected ReturnRequestRepository returnRequests;

    @Resource
    protected ReturnRepository returns;

    @Value("${oms.host}")
    protected String omsHost;

    @Value("${oms.base.path}")
    protected String omsBasePath;

    protected Long id;

    protected String number;

    protected Long returnRequestId;

    protected Long returnId;

    protected Long orderId;

    protected Long userId;

    protected Boolean takeResponsibilityRequired;

    protected Responsibility takeResponsibility;

    protected Long takeResponsibilityCauseId;

    protected Long takeResponsibilityUserId;

    protected String checkedBy;

    protected Status status;

    protected BigDecimal amount;

    protected BigDecimal actualAmount;

    protected BigDecimal paymentAmount;

    protected String paymentStatus;

    protected DateTime paymentPaidAt;

    protected Map<String, Object> ext;

    protected Map<String, Object> soInfo;

    protected Long venderId;

    protected DateTime updatedAt;

    protected DateTime createdAt;

    protected List<ReturnResultDetail> details;

    @Override
    public Map<String, Object> toJson() {
        Map<String, Object> json = new HashMap<>();
        json.put("id", id);
        Optional.ofNullable(number).ifPresent($ -> json.put("number", $));

        json.put("return_request", Maps.newHashMap("id", returnRequestId));
        json.put("return", Maps.newHashMap("id", returnId));

        Map<String, Object> user = Maps.newHashMap("id", userId);
        Optional.ofNullable(getMap(ext, "user")).ifPresent(user::putAll);
        json.put("user", user);

        Map<String, Object> order = Maps.newHashMap("id", orderId);
        Optional.ofNullable(getMap(ext, "order")).ifPresent(order::putAll);
        json.put("order", order);

        Map<String, Object> takeResponsibility = Maps.newHashMap("by", this.takeResponsibility, "required", takeResponsibilityRequired);
        Optional.ofNullable(takeResponsibilityUserId).ifPresent($ -> takeResponsibility.put("user_id", $));
        Optional.ofNullable(takeResponsibilityCauseId).ifPresent($ -> takeResponsibility.put("cause_id", $));
        json.put("take_responsibility", takeResponsibility);

        json.put("amount", amount);
        json.put("actual_amount", actualAmount);

        ofNullable(details).ifPresent(d ->
                json.put("aggregate", Maps.newHashMap(
                        "quantity", d.stream().filter($ -> !$.isWeighingGoods()).map($ -> $.quantity()).reduce(ZERO, BigDecimal::add),
                        "weight", d.stream().filter($ -> $.isWeighingGoods()).map($ -> $.realQuantity()).reduce(ZERO, BigDecimal::add)))
        );

        Map<String, Object> payment = Maps.newHashMap("status", paymentStatus);
        Optional.ofNullable(paymentAmount).ifPresent($ -> payment.put("amount", $));
        Optional.ofNullable(paymentPaidAt).ifPresent($ -> payment.put("paid_at", $));
        json.put("payment", payment);

        Optional.ofNullable(checkedBy).ifPresent($ -> json.put("checked_by", $));

        Optional.ofNullable(status).ifPresent($ -> json.put("status", $));

        Map<String, Object> return_ = Maps.newHashMap("id", returnId);
        Optional.ofNullable(getMap(ext, "return")).ifPresent(return_::putAll);
        json.put("return", return_);

        Optional.ofNullable(soInfo).ifPresent($ -> json.put("so_info", $));

        json.put("vender_id", venderId);
        json.put("updated_at", updatedAt);
        json.put("created_at", createdAt);

        ofNullable(details).ifPresent(details -> json.put("details", details.stream().map(item -> ((JsonType) item).toJson()).collect(toList())));

        return json;
    }

    protected String basePath() {
        return omsHost + omsBasePath;
    }

    @Override
    public void start(Map<String, Object> request) {
        String target = basePath() + "/returns/" + returnId + "/return-results/" + id();
        tasks.addHttpTrigger(id, venderId, Maps.newHashMap("method", "POST", "url", target + "/run", "entity", Collections.emptyMap()));
        tasks.addHttpTrigger(id, venderId, Maps.newHashMap("method", "POST", "url", target + "/issue", "entity", Collections.emptyMap()));
    }

    @Override
    public void issue(Map<String, Object> request) {
        mq.send(topic, Jsons.toJsonString(toJson()));
    }

    @Override
    public Map<String, List<Map<String, Object>>> relations() {
        Map<String, List<Map<String, Object>>> result = new HashMap<>();
        result.put("orders", orders.findOrders(orderId));
        result.put("return_requests", findReturnRequests());
        result.put("returns", findReturns());
        return result;
    }

    private List<Map<String, Object>> findReturnRequests() {
        Optional<Long> returnRequestId = ofNullable(getLong(ext, "return_request.id")).filter($ -> !getBoolean(ext, "return_request.check_immediately", false));
        Optional<ReturnRequest> returnRequest = returnRequestId.flatMap($ -> returnRequests.find($, false));
        return returnRequest.isPresent()
                ? Lists.newArrayList(Maps.of("id", returnRequest.get().id(), "number", returnRequest.get().number(), "status", returnRequest.get().status()))
                : Collections.emptyList();
    }

    private List<Map<String, Object>> findReturns() {
        Optional<Long> returnId = ofNullable(getLong(ext, "return.id")).filter($ -> getBoolean(ext, "return.return_required", true));
        Optional<Return> return_ = returnId.flatMap($ -> returns.find($, false));
        return return_.isPresent()
                ? Lists.newArrayList(Maps.of("id", return_.get().id(), "number", return_.get().number(), "status", return_.get().status()))
                : Collections.emptyList();
    }

    @Override
    public void updateActualAmount() {
        mapper.updateActualAmount(this);
    }

    @Override
    public Long id() {
        return id;
    }

    @Override
    public String number() {
        return number;
    }

    @Override
    public Status status() {
        return status;
    }

    public static class ReturnResultDetailRecord implements ReturnResultDetail, JsonType {

        @Resource
        protected ReturnResultMapper mapper;

        protected Long id;

        protected Long returnResultId;

        protected Long returnId;

        protected Long orderId;

        protected Integer itemCode;

        protected Long skuId;

        protected String skuName;

        protected Boolean isWeighingGoods;

        protected BigDecimal saleUnit;

        protected BigDecimal quantity;

        protected BigDecimal price;

        protected BigDecimal amount;

        protected BigDecimal actualAmount;

        protected Map<String, Object> ext;

        protected DateTime createdAt;

        protected DateTime updatedAt;

        @Override
        public Map<String, Object> toJson() {
            Map<String, Object> json = new HashMap<>();

            json.put("id", id);
            json.put("item_code", itemCode);
            json.put("sku", Maps.newHashMap("id", skuId, "name", skuName));
            json.put("is_weighing_goods", isWeighingGoods);
            json.put("sale_unit", saleUnit);

            json.put("ea_quantity", isWeighingGoods ? quantity : multiplyWithScale4(quantity, saleUnit));
            json.put("quantity", quantity);
            json.put("price", price);
            json.put("amount", amount);
            json.put("actual_amount", actualAmount);

            Optional.ofNullable(getMap(ext, "opinion")).ifPresent($ -> json.put("opinion", $));
            Optional.ofNullable(getMap(ext, "reason")).ifPresent($ -> json.put("reason", $));
            Optional.ofNullable(getMap(ext, "goods")).ifPresent($ -> json.put("goods", $));

            json.put("updated_at", updatedAt);
            json.put("created_at", createdAt);
            return json;
        }

        @Override
        public Long id() {
            return id;
        }

        @Override
        public BigDecimal amount() {
            return amount;
        }

        @Override
        public BigDecimal actualAmount() {
            return actualAmount;
        }

        @Override
        public boolean isWeighingGoods() {
            return isWeighingGoods;
        }

        @Override
        public BigDecimal quantity() {
            return isWeighingGoods ? divideWithScale4(quantity, saleUnit) : quantity;
        }

        @Override
        public BigDecimal realQuantity() {
            return isWeighingGoods ? quantity : multiplyWithScale4(quantity, saleUnit);
        }

        @Override
        public Long returnResultId() {
            return returnResultId;
        }

        @Override
        public void updateActualAmount() {
            mapper.updateDetailActualAmount(this);
        }
    }
}
